/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.commons.cryptography.providers;

import io.iec.edp.caf.commons.cryptography.model.CafKeyPair;
import io.iec.edp.caf.commons.cryptography.service.AsymmetricCryptoProvider;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;

import java.security.*;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * SM2国产非对称加密算法
 *
 * @author guowenchang
 */
@Slf4j
public class SM2CryptoProvider implements AsymmetricCryptoProvider {

    public static final int KEY_SIZE = 1024;
    public static final String ALGORITHM = "EC";
    public static final String SIGNATURE = "SHA1WithRSA";
    public static final String MODE = "SM2/ECB/NoPadding";

    @Override
    public CafKeyPair generateKeyPair() {
        try {
            // 获取SM2 椭圆曲线推荐参数
            X9ECParameters ecParameters = GMNamedCurves.getByName("sm2p256v1");
            // 构造EC 算法参数
            ECNamedCurveParameterSpec sm2Spec = new ECNamedCurveParameterSpec(
                    GMObjectIdentifiers.sm2p256v1.toString(),
                    ecParameters.getCurve(),
                    ecParameters.getG(),
                    ecParameters.getN());
            // 创建 密钥对生成器
            KeyPairGenerator gen = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());

            // 使用SM2的算法区域初始化密钥生成器
            gen.initialize(sm2Spec, new SecureRandom());
            // 获取密钥对
            KeyPair keyPair = gen.generateKeyPair();
            return new CafKeyPair(keyPair);
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            throw new RuntimeException("generate key failed.");
        }
    }

    @Override
    public byte[] encrypt(byte[] byteIn, byte[] key,boolean isReverse) {
        try {
            BCECPublicKey localECPublicKey = (BCECPublicKey) transformPublicKey(key);
            ECParameterSpec localECParameterSpec = localECPublicKey.getParameters();
            ECDomainParameters localECDomainParameters = new ECDomainParameters(
                    localECParameterSpec.getCurve(),
                    localECParameterSpec.getG(),
                    localECParameterSpec.getN());
            ECPublicKeyParameters localECPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), localECDomainParameters);

            SM2Engine localSM2Engine = new SM2Engine();
            localSM2Engine.init(true, new ParametersWithRandom(localECPublicKeyParameters, new SecureRandom()));
            return localSM2Engine.processBlock(byteIn, 0, byteIn.length);
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            throw new RuntimeException("transform failed.");
        }
    }

    @Override
    public byte[] decrypt(byte[] byteIn, byte[] key,boolean isReverse) {
        try {
            BCECPrivateKey localECPrivateKey = (BCECPrivateKey) transformPrivateKey(key);
            ECParameterSpec localECParameterSpec = localECPrivateKey.getParameters();
            ECDomainParameters localECDomainParameters = new ECDomainParameters(
                    localECParameterSpec.getCurve(),
                    localECParameterSpec.getG(),
                    localECParameterSpec.getN());
            ECPrivateKeyParameters localECPrivateKeyParameters = new ECPrivateKeyParameters(localECPrivateKey.getD(), localECDomainParameters);
            SM2Engine localSM2Engine = new SM2Engine();
            localSM2Engine.init(false, localECPrivateKeyParameters);
            return localSM2Engine.processBlock(byteIn, 0, byteIn.length);
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            throw new RuntimeException("transform failed.");
        }
    }

    @Override
    public byte[] sign(byte[] byteIn, byte[] key) {
        try {
            // 将byte[]的key格式化为PrivateKey
            PrivateKey privateKey = transformPrivateKey(key);
            // 生成SM2sign with sm3 签名验签算法实例
            Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), new BouncyCastleProvider());
            signature.initSign(privateKey);
            signature.update(byteIn);
            return signature.sign();
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            throw new RuntimeException("sign failed.");
        }
    }

    @Override
    public boolean verify(byte[] byteIn, byte[] sign, byte[] key) {
        try {
            // 取公钥对象
            PublicKey publicKey = transformPublicKey(key);
            Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), new BouncyCastleProvider());
            signature.initVerify(publicKey);
            signature.update(byteIn);
            // 验证签名是否正常
            return signature.verify(sign);
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            return false;
        }
    }

    private ECPublicKey transformPublicKey(byte[] key) {
        ECPublicKey publicKey;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            publicKey = (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(key));
            return publicKey;
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            throw new RuntimeException("transform key failed.");
        }
    }

    private ECPrivateKey transformPrivateKey(byte[] key) {
        ECPrivateKey privateKey;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            privateKey = (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(key));
            return privateKey;
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            throw new RuntimeException("transform key failed.");
        }
    }
}
